﻿using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace housingsafety.server.Caching.Core
{
    public class DistributedCacheFacade : ICache
    {
        private static readonly int _FlashMemeryCacheExpirationInSeconds = 5;
        private static readonly EventId _DefaultEevntId = 0;
        private readonly IMemoryCache m_FlashMemoryCache;
        private readonly IDistributedCache m_DistributedCache;
        private readonly ICacheSerializer m_Serializer;
        private readonly string m_Name;
        private readonly ILogger m_Logger;

        public string Name { get { return m_Name; } }

        public int FlashMemeryCacheExpirationInSeconds { get; set; } = _FlashMemeryCacheExpirationInSeconds;

        public EventId EventId { get; set; } = _DefaultEevntId;

        public DistributedCacheFacade(IMemoryCache flashMemoryCache, IDistributedCache distributedCache, ICacheSerializer serializer, ILogger logger, string name = null)
        {
            m_FlashMemoryCache = flashMemoryCache;
            m_DistributedCache = distributedCache;
            m_Serializer = serializer;
            m_Name = name ?? string.Empty;
            m_Logger = logger;
        }

        public TItem Get<TItem>(string key)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                object data = m_FlashMemoryCache.Get(key);
                if (data == null)
                {
                    var bytes = m_DistributedCache.Get(key);
                    if (bytes == null)
                        return default(TItem);
                    var item = m_Serializer.Deserialize<TItem>(bytes);
                    SetFlashMemoryCache(key, item);
                    return item;
                }
                return (TItem)data;
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "TItem Get<TItem> " + key);
            }
            return default(TItem);
        }

        public object Get(string key, Type type = null)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                var data = m_FlashMemoryCache.Get(key);
                if (data == null)
                {
                    var bytes = m_DistributedCache.Get(key);
                    if (bytes == null)
                        return null;
                    data = m_Serializer.Deserialize(bytes, type);
                    SetFlashMemoryCache(key, data);
                }
                return data;
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "object Get " + key);
            }
            return null;
        }

        public async Task<TItem> GetAsync<TItem>(string key)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                var data = m_FlashMemoryCache.Get(key);
                if (data == null)
                {
                    var bytes = await m_DistributedCache.GetAsync(key);
                    if (bytes == null)
                        return default(TItem);
                    var item = m_Serializer.Deserialize<TItem>(bytes);
                    SetFlashMemoryCache(key, item);
                    return item;
                }
                return (TItem)data;
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "Task<TItem> GetAsync<TItem> " + key);
            }
            return default(TItem);
        }

        public async Task<object> GetAsync(string key, Type type = null)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                var data = m_FlashMemoryCache.Get(key);
                if (data == null)
                {
                    var bytes = await m_DistributedCache.GetAsync(key);
                    if (bytes == null)
                        return null;
                    data = m_Serializer.Deserialize(bytes, type);
                    SetFlashMemoryCache(key, data);
                }
                return data;
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "Task<object> GetAsync " + key);
            }
            return null;
        }

        public void Remove(string key)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                m_FlashMemoryCache.Remove(key);
                m_DistributedCache.Remove(key);
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "void Remove " + key);
            }
        }

        public async Task RemoveAsync(string key)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            try
            {
                key = CreateCacheKeyString(key);
                m_FlashMemoryCache.Remove(key);
                await m_DistributedCache.RemoveAsync(key);
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "Task RemoveAsync " + key);
            }
        }

        public void Set<TItem>(string key, TItem value, CacheEntryOptions options = null)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
            try
            {
                key = CreateCacheKeyString(key);
                SetFlashMemoryCache(key, value);
                var data = m_Serializer.Serialize(value);
                m_DistributedCache.Set(key, data, new DistributedCacheEntryOptions()
                {
                    AbsoluteExpiration = options?.AbsoluteExpiration,
                    AbsoluteExpirationRelativeToNow = options?.AbsoluteExpirationRelativeToNow,
                    SlidingExpiration = options?.SlidingExpiration
                });
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "void Set<TItem> " + key);
            }
        }

        public async Task SetAsync<TItem>(string key, TItem value, CacheEntryOptions options = null)
        {
            Requires.NotNullOrEmpty(key, nameof(key));
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
            try
            {
                key = CreateCacheKeyString(key);
                SetFlashMemoryCache(key, value);
                var data = m_Serializer.Serialize(value);
                await m_DistributedCache.SetAsync(key, data, new DistributedCacheEntryOptions()
                {
                    AbsoluteExpiration = options?.AbsoluteExpiration,
                    AbsoluteExpirationRelativeToNow = options?.AbsoluteExpirationRelativeToNow,
                    SlidingExpiration = options?.SlidingExpiration
                });
            }
            catch (Exception ex)
            {
                m_Logger.LogError(EventId, ex, "Task SetAsync<TItem> " + key);
            }
        }
        public T GetOrDb<T>(string v, Func<string, T> p)
        {
            var t = Get<T>(v);
            if (null == t)
            {
                return p.Invoke(v);
            }

            return t;
        }
        private void SetFlashMemoryCache<TItem>(string key, TItem data)
        {
            m_FlashMemoryCache.Set(key, data, TimeSpan.FromSeconds(FlashMemeryCacheExpirationInSeconds));
        }

        private string CreateCacheKeyString(string key)
        {
            return CacheKey.NewCacheKeyString(string.Empty, m_Name, key);
        }
    }
}
